home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 11 - 1995 / 11.04 Apr 95 / Performance / Fractal 6 / FractalMain.c < prev   
Encoding:
C/C++ Source or Header  |  1995-01-22  |  19.2 KB  |  670 lines  |  [TEXT/MPS ]

  1. /*
  2.     File:        FractalMain.c
  3.  
  4.     Used to build:    “Fractal 6”
  5.     
  6.     Written by:        Jim Cathey            July 1985
  7.                     Eric Traut            November 1994
  8.  
  9.     Description:
  10.         The following code implements a “Fractal Contour” generating
  11.         program. The program was originally written in Microsoft BASIC
  12.         and ported to Aztec C in 1985 by Jim Cathey. It has been a
  13.         widely-distributed public domain application since that time.
  14.  
  15.         The program generates a fractal surface by starting with a 
  16.         triangular surface. It then subdivides the triangle into four
  17.         subtriangles by calculating the midpoint of each edge. The 
  18.         Z coordinate for each of these edges is then randomly incremented
  19.         or decremented. This processes is repeated for each of the
  20.         subtrianges until a jagged fractal surface results.
  21.         
  22.         The original program uses a “cordic” function to approximate
  23.         the calculation of various transcendental functions used in
  24.         three-dimensional graphics transforms.
  25.         
  26.         In 1994, Eric Traut modified the code to run under System 7.5.
  27.         The program was also updated so it could be recompiled under 
  28.         PowerPC. This source is part of a series of modifications made 
  29.         to demonstrate code optimizations under PowerPC.
  30.         
  31.         ------------------------------------------------------------
  32.         
  33.         The following documents the changes made to the program at
  34.         each step of the optimization process and lists the speed
  35.         as measured under a PowerMac 8100/80av (8-bit 13"monitor).
  36.         All stages were compiled using Metrowerks’ CodeWarrier
  37.         1.2 compilers.
  38.         
  39.         
  40.         • STAGE 1:
  41.         0.59 fractals per second
  42.         
  43.         The first step to optimizing for PowerPC is to clean up 
  44.         the source code so it compiles under today’s 68K compilers and
  45.         runs under today’s system software. I also added a feature
  46.         to allow repeated calculation of fractals and a display
  47.         of “fractals per second.” This value will be used to measure
  48.         our progress as we further optimize the program. Note that
  49.         this version runs emulated on a PowerMac.
  50.  
  51.         • STAGE 2:
  52.         2.83 fractals per second
  53.         4.80x emulated speed
  54.         
  55.         This version was created by simply recompiling stage 1 with the
  56.         PowerPC MetroWerks CodeWarrier C compiler. No other changes
  57.         were made.
  58.  
  59.         • STAGE 3:
  60.         2.97 fractals per second
  61.         5.03x emulated speed
  62.         1.05x original native speed
  63.         
  64.         This version was uses an off-screen GWorld for all drawing. This
  65.         naturally uses more memory, but QuickDraw can generally draw to
  66.         an off-screen GWorld faster because of caching characteristics
  67.         (the video buffer is mapped cache-write-through whereas the rest
  68.         of RAM is mapped cache-write-back). The program then uses 
  69.         QuickDraw’s CopyBits routine to copy the image to the screen.
  70.  
  71.         • STAGE 4:
  72.         3.47 fractals per second
  73.         5.88x emulated speed
  74.         1.23x original native speed
  75.         1.17x previous stage
  76.         
  77.         This version replaces QuickDraw’s LineTo with custom offscreen
  78.         line drawing functions. These functions ignore many of the complex
  79.         factors which QuickDraw considers including clipping, color mapping,
  80.         and patterns. Note that bypassing QuickDraw should not be used as
  81.         a general solution for improving performance, but for small specialized
  82.         portions of an app it is OK. Note that this code may run faster under
  83.         a graphics accellerator if QuickDraw were not bypassed.
  84.         
  85.         • STAGE 5:
  86.         6.69 fractals per second
  87.         11.34x emulated speed
  88.         2.36x original native speed
  89.         1.93x previous stage
  90.         
  91.         This version removes a call from the QuickDraw call “GetPixBaseAddr”
  92.         from the DoPlot routine. This routine is emulated on the first
  93.         PowerMacs, so it should be called as infrequently as possible. In
  94.         our case, we do not need to call it in such a tight loop. By moving
  95.         it to the PlotData function, we save considerable time. This version
  96.         also removes the calls to SetCursor to display the busy cursor. We
  97.         are now drawing fast enough that it makes no sense to display it.
  98.         
  99.         • STAGE 6:
  100.         10.92 fractals per second
  101.         18.51x emulated speed
  102.         3.86x original native speed
  103.         1.63x previous stage
  104.         
  105.         This version uses slightly modified routines for caculating and
  106.         plotting fractal surfaces. In general, short variables were changed
  107.         to long. Divisions are avoided wherever possible. All local functions
  108.         are defined as static. Points are drawn with a special function 
  109.         instead of going through the more complex LineTo function. Scaling
  110.         is done with shifts rather than multiplications and divisions. This
  111.         results in a much better performance, but the scaling is not as
  112.         smooth.
  113.         
  114. */
  115.  
  116.  
  117. #include <Types.h>
  118. #include <Memory.h>
  119. #include <Quickdraw.h>
  120. #include <Fonts.h>
  121. #include <Windows.h>
  122. #include <OSUtils.h>
  123. #include <Menus.h>
  124. #include <Events.h>
  125. #include <TextEdit.h>
  126. #include <Dialogs.h>
  127. #include <Desk.h>
  128. #include <Controls.h>
  129. #include <ToolUtils.h>
  130. #include <Resources.h>
  131. #include <Strings.h>
  132. #include <QDOffscreen.h>
  133.  
  134. #include <stdio.h>
  135.  
  136. #include "Fractal.h"
  137.  
  138. /* Functions defined within the file */
  139. void InitApp(void);
  140. void SetUpMenus(void);
  141. void SetUpWindow(void);
  142. void MainEventLoop(void);
  143. void UpdateWindow(WindowPtr theWindow);
  144. void DrawTimeInfo(void);
  145. Boolean DoMenuCommand(long mresult);
  146. void DoSetUpDialog(void);
  147. void SetTerrainButton(DialogPtr ptr, short oncontrl, short offcontrl);
  148. void DoAboutBox(void);
  149. void ReportFatalError(void);
  150. void CleanUpApp(void);
  151.  
  152. /* Global variables */
  153. short             (*gPointArray)[kMaxXPoint][kMaxYPoint] = NULL;            
  154.                                         /* The array of points to be subdivided */
  155. short             gContourType;                /* Contour type */
  156. short             gContourLevel;                /* Level of detail */
  157. WindowPtr         gMainWindow = NULL;            /* Our one window */
  158. short            gMainWindowHeight;            /* Height of main window */
  159. short            gMainWindowWidth;            /* Width of main window */
  160. Boolean            gContinuousRedraw;            /* Continuously redraw fractal */
  161. long            gTotalTickCount;            /* Total time spent calculating and drawing */
  162. long            gTotalFractals;                /* Total fractal count */
  163. Boolean            gFractalChanged;            /* Has fractal changed since last update? */
  164. GWorldPtr        gOffscreenGWorld = NULL;    /* GWorld for faster drawing */
  165. PixMapHandle    gOffscreenPixMap = NULL;    /* GWorld’s Pix Map */
  166.  
  167. /*
  168.     main
  169. */
  170. void main(void) 
  171. {
  172.     InitApp();
  173.     MainEventLoop();
  174.     CleanUpApp();
  175. }
  176.  
  177.  
  178. /*
  179.     CleanUpApp
  180. */  
  181. void CleanUpApp(void)
  182. {
  183.     if (gOffscreenGWorld)
  184.         DisposeGWorld(gOffscreenGWorld);
  185.     
  186.     if (gMainWindow)
  187.         DisposeWindow(gMainWindow);
  188.     
  189.     if (gPointArray)
  190.         DisposePtr((Ptr)gPointArray);
  191.     
  192.     ExitToShell();
  193. }
  194.  
  195.  
  196. /*
  197.     MainEventLoop
  198. */  
  199. void MainEventLoop(void)
  200. {
  201.     EventRecord         curEvent;            /* Event we should respond to */
  202.     WindowPtr             whichWindow;        /* Window which received the click */
  203.     Boolean             userDone;            /* Should we quit the program? */
  204.  
  205.     userDone = false;
  206.  
  207.     /* Get next event, and handle it appropriately, until user quits */
  208.     while (!userDone) {
  209.         
  210.         SetPort(gMainWindow);
  211.         if (WaitNextEvent(everyEvent, &curEvent, 1, NULL)) {
  212.             switch (curEvent.what) {
  213.             case mouseDown:
  214.                 switch (FindWindow(curEvent.where, &whichWindow)) {
  215.                 case inSysWindow:    /* handle the desk accessories */
  216.                     SystemClick(&curEvent, whichWindow);
  217.                     break;
  218.                 case inMenuBar:     /* handle the command */
  219.                     userDone = DoMenuCommand(MenuSelect(curEvent.where));
  220.                     break;
  221.                 case inDrag:         /* No Drag region, treat as content */
  222.                     {
  223.                         Rect        limitRect;
  224.         
  225.                         limitRect = qd.screenBits.bounds;
  226.                         InsetRect(&limitRect, 4, 4);
  227.                         DragWindow(gMainWindow, curEvent.where, &limitRect);
  228.                         break;
  229.                     }
  230.                 case inContent:        /* Activate window */
  231.                     if (whichWindow == gMainWindow)
  232.                         if (whichWindow != FrontWindow())
  233.                             SelectWindow(whichWindow);
  234.                     break;
  235.                 case inGrow:         /* No Grow Region */
  236.                 case inGoAway:         /* We don’t have a GoAway region */
  237.                             break;
  238.                 }
  239.                 break;
  240.  
  241.             case keyDown: 
  242.             case autoKey:            /* If command key, pass the char to MenuKey */
  243.                 if (curEvent.modifiers & cmdKey) 
  244.                     userDone = DoMenuCommand(MenuKey((char)(curEvent.message & charCodeMask)));
  245.                 break;
  246.             case updateEvt:            /* If it’s for our window, update it */
  247.                 if ((WindowPtr)curEvent.message == gMainWindow)
  248.                     UpdateWindow(gMainWindow);
  249.                     break;
  250.             case activateEvt:         /* If for our window, set port as necessary */
  251.                 if ((WindowPtr)curEvent.message == gMainWindow) {
  252.                     if (curEvent.modifiers & 1) {
  253.                                     /* odd means an activate event */
  254.                         SetPort(gMainWindow);
  255.                         DisableItem(GetMenu(kEditMenuID), 0);
  256.                         EnableItem(GetMenu(kFileMenuID), 0);
  257.                         EnableItem(GetMenu(kOptionsMenuID), 0);
  258.                         DrawMenuBar();
  259.                     }
  260.                     else {
  261.                         EnableItem(GetMenu(kEditMenuID), 0);
  262.                         DisableItem(GetMenu(kFileMenuID), 0);
  263.                         DisableItem(GetMenu(kOptionsMenuID), 0);
  264.                         DrawMenuBar();
  265.                     }
  266.                 }
  267.                 break;
  268.             }
  269.         }
  270.         else {
  271.             /* If there is no other event to handle (i.e. we got a null event)
  272.                 and we are in “continuous redraw” mode, we can calculate and display
  273.                 the next fractal. */
  274.             if (gContinuousRedraw && !gFractalChanged) {
  275.                 CalcSurface(gContourLevel);
  276.                 InvalRect(&gMainWindow->portRect);
  277.             }
  278.         }    
  279.     }
  280. }
  281.  
  282.  
  283. /*
  284.     DoMenuCommand
  285.  
  286.    This function responds to the menu command returned by MenuSelect.
  287.    If it was Quit, we return true, else false.  Since the menu was
  288.    highlighted by MenuSelect, we must finish by unhighlighting it
  289.    to indicate we're done.
  290. */
  291. Boolean DoMenuCommand(long menuResult)
  292. {
  293.     short            menuID;                /* menu ID of selected menu */
  294.     short            itemNumber;            /* item number of selected menu item */
  295.     Boolean            quitSelected;        /* was menu item Quit? */
  296.  
  297.     quitSelected = false;                /* Assume Quit not selected */
  298.     menuID = HiWord(menuResult);        /* Get the menu selected */
  299.     itemNumber = LoWord(menuResult);    /* ... and the item of that menu */
  300.  
  301.     switch (menuID) {
  302.     case kAppleMenuID: 
  303.         if (itemNumber == kAboutBoxItem)    /* Tell about FracCont */
  304.             DoAboutBox();
  305.         else {                              /* Run a desk accessory */
  306.             Str255            menuName;
  307.             GrafPtr         savedPort;
  308.                 
  309.             GetPort(&savedPort);            /* Preserve port */
  310.             GetMenuItemText(GetMenu(kAppleMenuID), itemNumber, menuName);
  311.             (void) OpenDeskAcc(menuName);    /* Run the desk accessory */
  312.             SetPort(savedPort);                /* Restore port */
  313.         }
  314.         break;
  315.  
  316.         case kFileMenuID: 
  317.             switch (itemNumber) {
  318.                 case kNewFractalItem:       /* New Surface */
  319.                         CalcSurface(gContourLevel);
  320.                         InvalRect(&gMainWindow -> portRect);
  321.                         break;
  322.                 case kQuitItem: 
  323.                     quitSelected = true;    /* Quit */
  324.                     break;
  325.             }
  326.             break;
  327.  
  328.         case kOptionsMenuID:
  329.             switch (itemNumber) {
  330.             case kSetupItem:
  331.                 DoSetUpDialog();
  332.                 break;
  333.             case kContinuousItem:
  334.                 SetItemMark(GetMenu(kOptionsMenuID), kContinuousItem, gContinuousRedraw ?
  335.                         noMark : checkMark);
  336.                 gContinuousRedraw = !gContinuousRedraw;
  337.                 break;
  338.             }
  339.         break;
  340.     }
  341.  
  342.     HiliteMenu(0);                            /* Turn off hilighting on the menu just used */
  343.     return quitSelected;
  344. }
  345.  
  346.  
  347. /*
  348.     SetTerrainButton
  349.     
  350.     This function is called by the DoSetUpDialog function to change
  351.     the current terrain button.
  352. */
  353. void SetTerrainButton(DialogPtr theDialog, short newControlID, short oldControlID)
  354. {
  355.     short                itemType;
  356.     ControlHandle        theControl;
  357.     Rect                itemBox;
  358.  
  359.     GetDialogItem(theDialog, oldControlID, &itemType, (Handle*)&theControl, &itemBox);
  360.     SetCtlValue(theControl, 0);     /* Turn off old default button */
  361.     GetDialogItem(theDialog, newControlID, &itemType, (Handle*)&theControl, &itemBox);
  362.     SetCtlValue(theControl, 1);     /* Turn on default button */
  363. }
  364.         
  365.  
  366. /*
  367.     DoSetUpDialog
  368.     
  369.     This function displays the set-up dialog and handles user actions
  370.     while the dialog is up.
  371. */
  372. void DoSetUpDialog(void)
  373. {
  374.     DialogPtr         theDialog;                /* Pointer to dialog */
  375.     long             newType;                /* Selected contour type */
  376.     long             newLevel;                /* Selected level of detail */
  377.     Boolean         doAnother;                /* True if we need to recalc */
  378.     short             itemHit = 0;            /* Item number of selected item */
  379.     Str255             itemText;                /* Used for converting string to num */
  380.     short             itemType;                /* Dummy variable needed for GetDialogItem */
  381.     Handle            itemHandle;                /* Item handle returned by GetDialogItem */
  382.     Rect            itemBox;                /* Item bound box returned by GetDialogItem */
  383.         
  384.     newType = gContourType;                    /* Initialize local vars to current values */
  385.     newLevel = gContourLevel;
  386.     doAnother = false;
  387.  
  388.     /* Display the dialog and set initial values if controls */
  389.     theDialog = GetNewDialog(kSetUpDialogID, NULL, (WindowPtr) -1);
  390.     SetPort(theDialog);
  391.     
  392.     SetTerrainButton(theDialog, gContourType, gContourType);
  393.     NumToString(newLevel, itemText);
  394.     GetDialogItem(theDialog, kSetUpLevelID, &itemType, &itemHandle, &itemBox);
  395.     SetDialogItemText(itemHandle, itemText);
  396.     SelIText(theDialog, kSetUpLevelID, 0, 32767);    /* Hilite entire text field. */
  397.     
  398.     while (itemHit != kSetUpOKButtonID && itemHit != kSetUpCancelButtonID) {
  399.         ModalDialog(NULL, &itemHit);
  400.         GetDItem(theDialog, kSetUpLevelID, &itemType, &itemHandle, &itemBox);
  401.         GetIText(itemHandle, itemText);                /* Get Level field */
  402.         StringToNum(itemText, &newLevel);
  403.         
  404.         /* Check for legal contour level */
  405.         if (newLevel < 1 || newLevel > kMaxLevel) {
  406.             SysBeep(0);
  407.             newLevel = gContourLevel;
  408.             NumToString(newLevel, itemText);
  409.             GetDialogItem(theDialog, kSetUpLevelID, &itemType, &itemHandle, &itemBox);
  410.             SetDialogItemText(itemHandle, itemText);
  411.             SelIText(theDialog, kSetUpLevelID, 0, 100);  /* Hilite text field. */
  412.         }
  413.  
  414.         if (itemHit == kSetUpMtnButtonID || 
  415.                 itemHit == kSetUpHillsButtonID ||
  416.                 itemHit == kSetUpWaterButtonID) {
  417.             SetTerrainButton(theDialog, itemHit, newType);
  418.             newType = itemHit;
  419.         }
  420.  
  421.         if (itemHit == kSetUpOKButtonID && ((newLevel > 0) & (newLevel <= kMaxLevel))) {
  422.             if (gContourType != newType || gContourLevel != newLevel) {
  423.                 doAnother = true;
  424.                 SetPort(gMainWindow);
  425.                 InvalRect(&gMainWindow->portRect);
  426.                 gContourLevel = newLevel;
  427.                 gContourType = newType;
  428.             }
  429.         }
  430.     }
  431.     
  432.     DisposeDialog(theDialog);        /* Release storage and remove dialog from screen */
  433.  
  434.     if (doAnother) {
  435.         gTotalTickCount = 0;
  436.         gTotalFractals = 0;
  437.         CalcSurface(newLevel);
  438.         InvalRect(&gMainWindow->portRect);
  439.         UpdateWindow(gMainWindow);
  440.     }
  441. }
  442.  
  443.  
  444. /*
  445.     DoAboutBox
  446.     
  447.     This function displays the about box dialog.
  448. */
  449. void DoAboutBox(void)
  450. {
  451.     short            itemHit;            
  452.     DialogPtr        theDialog;
  453.  
  454.     theDialog = GetNewDialog(kAboutBox1DialogID, NULL, (WindowPtr) -1);
  455.     if (theDialog == NULL)
  456.         ReportFatalError();
  457.         
  458.     SetPort(theDialog);
  459.     
  460.     ModalDialog(NULL, &itemHit);
  461.     DisposeDialog(theDialog);
  462.  
  463.     if (itemHit == kAboutBoxMoreButtonID) {
  464.         theDialog = GetNewDialog(kAboutBox2DialogID, NULL, (WindowPtr) -1);
  465.         ModalDialog(NULL, &itemHit);
  466.         DisposeDialog(theDialog);
  467.     }
  468. }
  469.  
  470.  
  471. /*
  472.     SetUpMenus
  473.     
  474.     This function initilizes the menu bar and the applications menus.
  475. */
  476. void SetUpMenus(void)
  477.  {
  478.     SetMenuBar(GetNewMBar(kMenuBarID));
  479.     AddResMenu(GetMenu(kAppleMenuID), 'DRVR');
  480.     DrawMenuBar();
  481. }
  482.  
  483.  
  484. /*
  485.     SetUpWindow
  486.     
  487.     This function initializes the main window and its associated
  488.     global variables. It also sets up the offscreen GWorld for
  489.     our drawing and screen updates.
  490. */
  491. void SetUpWindow(void)
  492. {
  493.     GDHandle            mainDeviceHandle;
  494.     short                maxWindowHeight, maxWindowWidth;
  495.     Rect                offscreenRect;
  496.  
  497.     gMainWindow = GetNewCWindow(kMainWindowID, NULL, (WindowPtr)-1);
  498.      mainDeviceHandle = GetMainDevice();
  499.      
  500.     maxWindowHeight = (*mainDeviceHandle)->gdRect.bottom - (*mainDeviceHandle)->gdRect.top - 
  501.              (2 * kScreenBoundaryBits) - GetMBarHeight() - kWindowTitleHeight;
  502.      maxWindowWidth = (*mainDeviceHandle)->gdRect.right - (*mainDeviceHandle)->gdRect.left - 
  503.              (2 * kScreenBoundaryBits);
  504.      
  505.      if (maxWindowHeight >= kNewScreenY && maxWindowWidth >= kNewScreenX) {
  506.          gMainWindowHeight = kNewScreenY;
  507.          gMainWindowWidth = kNewScreenX;
  508.      }
  509.      else {
  510.          gMainWindowHeight = maxWindowHeight;
  511.          gMainWindowWidth = maxWindowWidth;
  512.      }
  513.      
  514.      SizeWindow(gMainWindow, gMainWindowWidth, gMainWindowHeight, false);
  515.      MoveWindow(gMainWindow,
  516.              (*mainDeviceHandle)->gdRect.left + kScreenBoundaryBits,
  517.              (*mainDeviceHandle)->gdRect.top + kScreenBoundaryBits + GetMBarHeight() + kWindowTitleHeight,
  518.              false);
  519.  
  520.     /* Now, allocate the offscreen GWorld */
  521.      SetRect(&offscreenRect, 0, 0, kNewScreenX, kNewScreenY);
  522.      (void) NewGWorld(&gOffscreenGWorld, 8, &offscreenRect, NULL, mainDeviceHandle, 0);
  523.     
  524.     if (gOffscreenGWorld == NULL)
  525.         ReportFatalError();
  526.  
  527.      if (gOffscreenGWorld)
  528.         gOffscreenPixMap = GetGWorldPixMap(gOffscreenGWorld);
  529.  
  530.      ShowWindow(gMainWindow);
  531. }
  532.  
  533.  
  534. /*
  535.     InitApp
  536.     
  537.     This function initializes the program the Mac toolbox.
  538. */
  539. void InitApp(void)
  540. {
  541.     InitGraf(&qd.thePort);
  542.     InitFonts();
  543.     InitWindows();
  544.     InitMenus();
  545.     TEInit();
  546.     InitDialogs(NULL);
  547.     InitCursor();
  548.  
  549.      gContinuousRedraw = false;
  550.     gTotalTickCount = 0;                    /* Initialize performance counters */
  551.     gTotalFractals = 0;
  552.  
  553.     gContourType = kDefaultStyle;            /* Default style */
  554.     gContourLevel = kDefaultLevel;            /* Default Level */
  555.  
  556.     SetUpMenus();                            /* Set up our menus */
  557.  
  558.     /* Allocate the data array. */
  559.     gPointArray = (void*)NewPtr((long) kMaxXPoint*kMaxYPoint*(sizeof(short)));
  560.  
  561.     /* Make sure we got some memory */
  562.     if (gPointArray == NULL)
  563.         ReportFatalError();
  564.  
  565.      SetUpWindow();                            /* Create new window */
  566.  
  567.     CalcSurface(gContourLevel);             /* Do at least one first. */
  568. }
  569.  
  570.  
  571. /*
  572.     UpdateWindow
  573.  
  574.     This is our response to receipt of an update event for 
  575.     gMainWindow. It redraws our main window, respecting the
  576.     current clip region, etc.
  577. */
  578. void UpdateWindow(WindowPtr theWindow)
  579. {
  580.     GrafPtr                savedPort;            /* Used to temporarily save cur port */
  581.     Rect                windowRect;            /* Rect of entire window */
  582.     long                startTicks;            /* Used for performance timing */
  583.  
  584.     GetPort(&savedPort);            /* Save current port */
  585.     SetPort(theWindow);                /* Work in the specified window */
  586.     BeginUpdate(theWindow);
  587.     
  588.     startTicks = TickCount();        /* Record current tick count */
  589.     
  590.     if (gFractalChanged) {
  591.         GDHandle            curGDevice;
  592.     
  593.         curGDevice = GetGDevice();
  594.         SetGWorld((CGrafPtr)gOffscreenGWorld, NULL);
  595.         SetRect(&windowRect, 0, 0, kNewScreenX, kNewScreenY);
  596.         EraseRect(&windowRect);
  597.         PlotData();                        /* Redraw contents of window */
  598.         SetGDevice(curGDevice);
  599.     }
  600.  
  601.     SetPort(gMainWindow);
  602.     LockPixels(gOffscreenGWorld->portPixMap);
  603.     CopyBits((BitMap*)*gOffscreenGWorld->portPixMap, 
  604.             &gMainWindow->portBits,
  605.             &(*gOffscreenGWorld->portPixMap)->bounds,
  606.             &gMainWindow->portRect,
  607.             srcCopy,
  608.             NULL);
  609.     UnlockPixels(gOffscreenGWorld->portPixMap);
  610.     
  611.     /* If this is an update in response to a recomputation, we will count it
  612.         as a part of the total time for the current fractal. If the update is
  613.         in response to something else (e.g. the window coming to the front),
  614.         we won’t time it. */
  615.     if (gFractalChanged) {
  616.         gFractalChanged = false;
  617.         gTotalTickCount += TickCount() - startTicks;
  618.     }
  619.     
  620.     DrawTimeInfo();                    /* Draw performance statistics */
  621.     
  622.     SetPort(savedPort);                /* Restore previous port */
  623.     EndUpdate(theWindow);
  624. }
  625.  
  626.  
  627. /*
  628.     DrawTimeInfo
  629. */
  630. void DrawTimeInfo(void)
  631. {
  632.     Str255        tempString;            /* Used for creating statistics string */
  633.     Rect        tempRect;            /* Rect to erase */
  634.  
  635.     TextFont(geneva);
  636.     TextSize(12);
  637.     MoveTo(20, gMainWindowHeight - 5);
  638.     DrawString("\pFractals per second: ");
  639.  
  640.     if (gTotalTickCount != 0) {        /* Make sure we don’t divide by zero */
  641.         SetRect(&tempRect, 150, gMainWindowHeight - 20, 200, gMainWindowHeight);
  642.         EraseRect(&tempRect);
  643.         sprintf((char*)tempString, "%3.3f", 
  644.                 (double)gTotalFractals * 60 / (double)gTotalTickCount);
  645.         c2pstr((char*)tempString);
  646.         DrawString(tempString);
  647.     }
  648. }
  649.  
  650.  
  651.  
  652. /*
  653.     ReportFatalError
  654.     
  655.     This function displays a fatal error alert and terminates the program.
  656. */
  657. void ReportFatalError(void)
  658. {
  659.     (void) Alert(kFatalErrorAlertID, NULL);
  660.     CleanUpApp();
  661. }
  662.  
  663.  
  664.  
  665.  
  666.  
  667.  
  668.  
  669.  
  670.